package raytracer.misc;

import java.util.ArrayList;

import raytracer.main.Scene;
import raytracer.math.Vector3d;
import raytracer.objects.BoundingBox;
import raytracer.objects.SpaceStructure;
import raytracer.objects.ThreeDObject;

public class KDNode extends SpaceStructure
{

	private static ArrayList<KDNode> testList = new ArrayList<KDNode>();
	private int count = 0;
	public KDNode child1,child2;
	public int splitAxis; // 0 = x, 1 = y, 2 = z
	public int objectPosition; //0 = on axis, 1 = smaller than axis, 2 = bigger than axis
	public ArrayList<ThreeDObject> objectList;
	public double splitPoint;
	BoundingBox bBox;
	
	public KDNode(Scene scene)
	{
		createKDTree(scene.getObjectList(), 0);
		
	}
	
	KDNode(ArrayList<ThreeDObject> objectList, KDNode child1, KDNode child2, int splitAxis, double splitPoint, BoundingBox bBox)
	{
		this.objectList = objectList;
		this.child1 = child1;
		this.child2 = child2;
		this.splitAxis = splitAxis;
		this.splitPoint = splitPoint;
		this.bBox = bBox;
			
	}
	
	public static KDNode createKDTree(ArrayList<ThreeDObject> objectList, int depth){
		
		//get correct axis
		int splitAxis = depth%3;
		int objectPosition = 0;
		double splitPoint = 0;
		ArrayList<ThreeDObject> ch1 = new ArrayList<ThreeDObject>();
		ArrayList<ThreeDObject> ch2 = new ArrayList<ThreeDObject>();
		
		//reached maximum tree criteria, create leaf
		if(objectList.size() <= AppWideConstants.MAX_OBJECTS_PER_NODE || depth >= AppWideConstants.MAX_KDTREE_DEPTH){

			KDNode node = new KDNode(objectList, null, null, splitAxis, 0, new BoundingBox(objectList));
			System.out.println("Neues Blatt mit "+objectList.size()+" Objekten: "+ node);
			if(objectList.size() > 100)
			{
				testList.add(node);
				System.out.println("Testlistsize "+testList.size());
			}
			return node;
		}
		else{
			//where to split the axis
			splitPoint = spaceMean(objectList, splitAxis);
			for(ThreeDObject obj : objectList){
				//determine if object is on, left or right of that point on the axis and set object lists accordingly
				objectPosition = getObjectPosition(obj, splitPoint, splitAxis);
				switch (objectPosition) {
				case 0:
					ch1.add(obj);
					ch2.add(obj);
					break;
					
				case 1:
					ch1.add(obj);
					break;
				
				case 2:
					ch2.add(obj);
					break;
					
				default:
					break;
				}
			}
			
		}
		return new KDNode(null, createKDTree(ch1, depth+1), createKDTree(ch2,depth+1), splitAxis, splitPoint, new BoundingBox(objectList));

	}
	
	private static int getObjectPosition(ThreeDObject obj, double intersectPoint, int intersectAxis) {
		
		int erg = 0;
		
		switch (intersectAxis) {
		case 0:
			if(obj.getMaxX() <= intersectPoint){
				erg = 2;
			}
			else if(obj.getMinX() >= intersectPoint){
				erg = 1;
			}
			else{
				erg = 0;
			}
				
			break;
			
		case 1:
			if(obj.getMaxY() <= intersectPoint){
				erg = 2;
			}
			else if(obj.getMinY() >= intersectPoint){
				erg = 1;
			}
			else{
				erg = 0;
			}
			
			break;
			
		case 2:
			if(obj.getMaxZ() <= intersectPoint){
				erg = 2;
			}
			else if(obj.getMinZ() >= intersectPoint){
				erg = 1;
			}
			else{
				erg = 0;
			}
			
			break;
			
		default:
			erg = 0;
			break;
		}
		if(erg == 0) System.out.println(erg);
		return erg;
	}

	private static double spaceMean(ArrayList<ThreeDObject> objectList, int intersectAxis) {
		
		BoundingBox bb = new BoundingBox(objectList);
		double erg;
		
		switch (intersectAxis) {
		case 0:
			erg = (bb.getMaxX() + bb.getMinX())/2;
			break;
			
		case 1:
			erg = (bb.getMaxY() + bb.getMinY())/2;
			break;
			
		case 2:
			erg = (bb.getMaxZ() + bb.getMinZ())/2;
			break;
			
		default:
			erg = 0;
			break;
		}
		
		return erg;
	}

	@Override
	public ThreeDObject intersect(Ray ray, Vector3d intersectionPoint,Vector3d normal)
	{
		Double t = null;
		Double minDistance=Double.MAX_VALUE;
		ThreeDObject intersectedObj = null;
		double tMin, tMax, tAtSplit = 0;
		
		//No children means leaf, check for intersection of all objects in leaf
		if(isLeaf()){
			if(testList.contains(this))
			{
				System.out.println("Objekt aus Testlist wird geschnitten");
			}
			for(ThreeDObject obj : objectList){
				if ((t=obj.intersect(ray,intersectionPoint,normal))!=null) {
					if(t<minDistance && t-AppWideConstants.EPSILON>0){
						minDistance = t;
						intersectedObj = obj;
						
					}
				}
			}
			
			return intersectedObj;
		}
		else
		{
			int axis = splitAxis;
			double rayP, rayIV;
			
			//get tMin, tMax, rayP and ray_iv of plane, for calculation after gordon stoll siggraph 2006
			switch (axis) {
			case 0:
				tMin = bBox.getMinX();
				tMax = bBox.getMaxX();
				rayP = ray.getOrigin().x;
				rayIV = 1/ray.getDirection().x;
				break;
				
			case 1:
				tMin = bBox.getMinY();
				tMax = bBox.getMaxY();
				rayP = ray.getOrigin().y;
				rayIV = 1/ray.getDirection().y;
				break;
				
			case 2:
				tMin = bBox.getMinZ();
				tMax = bBox.getMaxZ();
				rayP = ray.getOrigin().z;
				rayIV = 1/ray.getDirection().z;
				break;
				
			default:
				tMax = tMin = rayP = rayIV = 0;
				break;
			}
			
			double[] tMinMax = this.bBox.intersect(ray);
			
			if(tMinMax == null)
			{
				return null;
			}
			else
			{
				tMin = tMinMax[0];
				tMax = tMinMax[1];
			}
			
			//calculation after Gordon Stoll
			tAtSplit = (splitPoint - rayP) * rayIV;
//			System.out.println("tAtSplit:" + tAtSplit);
//			System.out.println("tMid:" + splitPoint);
//			System.out.println("tMin:" + tMin);
//			System.out.println("tMax:" + tMax);
			
			if(tAtSplit > tMin && tAtSplit < tMax)
			{
//				System.out.println("Fall1 "+this);
				intersectedObj = child1.intersect(ray, intersectionPoint, normal);
				if(intersectedObj != null)
				{
					return intersectedObj;
				}
				else
				{
					return child2.intersect(ray, intersectionPoint, normal);
				}
			}
			
			if(tAtSplit < tMin && tAtSplit > tMax)
			{
//				System.out.println("Fall2 "+this);
				intersectedObj = child2.intersect(ray, intersectionPoint, normal);
				if(intersectedObj != null)
				{
					return intersectedObj;
				}
				else
				{
					return child1.intersect(ray, intersectionPoint, normal);
				}
			}
			
			if(tAtSplit < tMin && tAtSplit < tMax)
			{
//				System.out.println("Fall3 "+this);
				return child2.intersect(ray, intersectionPoint, normal);
			}
			
			if(tAtSplit > tMin && tAtSplit > tMax)
			{
//				System.out.println("Fall4 "+this);
				return child1.intersect(ray, intersectionPoint, normal);
			}
			
			
//			if(splitPoint >= tMin){
//			}
//			if(splitPoint <= tMax ){
//				intersectedObj = child2.intersect(ray, intersectionPoint, normal);
//			}
//			
//			intersectedObj = child2.intersect(ray, intersectionPoint, normal);
//			if(intersectedObj == null){
//				intersectedObj = child1.intersect(ray, intersectionPoint, normal);
//			}
			
		}

//		return intersectedObj;
		return null;
	}
	
	public boolean isLeaf()
	{
		return (child1 == null)? true:false;
	}

}
